﻿using System;
using System.Reflection;
using Moq;

namespace UnitTesting.Fixturing.Moq
{
	class FixtureMockActivator : IActivator
	{
		object _mock;
		Type _type;

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")]
		public object Create(ActivatorParams activatorParams)
		{
			EnsureIsValid(activatorParams);

			_type = activatorParams.TargetType;

			return Ensure.IsNotNull(activatorParams.TargetType, "type").IsSealed
			       	? CreateSealedInstance(activatorParams)
			       	: CreateNotSealedInstance(activatorParams);
		}

		static void EnsureIsValid(ActivatorParams activatorParams)
		{
			if (activatorParams == null) throw new ArgumentNullException("activatorParams");
			Ensure.IsNotNull(activatorParams.ValueProvider, "valueProvider");
			Ensure.IsNotNull(activatorParams.Arguments, "arguments");
			Ensure.IsNotNull(activatorParams.FixtureOptions, "fixtureOptions");
			Ensure.IsNotNull(activatorParams.Freezable, "Freezable");
			Ensure.IsNotNull(activatorParams.TargetType, "TargetType");

			if (!(activatorParams.FixtureOptions is MockSettings))
			{
				throw new ArgumentException("FixtureOptions should be mock settings class.");
			}
		}

		static object CreateSealedInstance(ActivatorParams activatorParams)
		{
			return DefaultActivator.Instance.Create(activatorParams);
		}

		object CreateNotSealedInstance(ActivatorParams activatorParams)
		{
			_mock = MockCreator.CreateMock(activatorParams);
			return CreateMockObject();
		}

		object CreateMockObject()
		{
			var typeOfObject = typeof (FixtureMock<>).MakeGenericType(_type);
			var property = _mock.GetType().GetProperty("Object", typeOfObject);
			return property.GetValue(_mock, null);
		}

		static class MockCreator
		{
			internal static object CreateMock(ActivatorParams activatorParams)
			{
				var mockSettings = (MockSettings) activatorParams.FixtureOptions;
				var mock = (Mock) CreateMockConstructor(activatorParams.TargetType).Invoke(
					new object[] {activatorParams.ValueProvider, activatorParams.Freezable, mockSettings.MockBehavior, activatorParams.Arguments});
				SetMockSettings(mock, mockSettings);
				return mock;
			}

			static ConstructorInfo CreateMockConstructor(Type type)
			{
				var mockGenericType = typeof (FixtureMock<>).MakeGenericType(type);
				const BindingFlags bindingFlags = BindingFlags.Instance
				                                  | BindingFlags.Public
				                                  | BindingFlags.NonPublic;
				return mockGenericType.GetConstructor(
					bindingFlags,
					null,
					new[] {typeof (IValueProvider), typeof (IFreezable), typeof (MockBehavior), typeof (object[])},
					null);
			}

			static void SetMockSettings(Mock mock, MockSettings mockSettings)
			{
				mock.DefaultValue = (DefaultValue) mockSettings.FixtureMockDefaultValue;
				mock.CallBase = mockSettings.MockCallBase;
			}
		}
	}
}